﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

using Storm.TextEditor.Editor;
using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.Objects.Collections;

namespace Storm.TextEditor.Parser.XML
{
	/// <summary>
	/// Represents a language defined in an XML sheet.
	/// </summary>
	public class Language
	{
		#region Fields

		private string name = "";
		private string separators = " .,:;{}()[]+-*/\\ \t=&%$#@!|&";

		private long version = long.MinValue;

		private Hashtable blocks = new Hashtable();
		private Hashtable styles = new Hashtable();

		private Block mainBlock = null;
		private ArrayList fileTypes = new ArrayList();
        private LanguageCollection derivedLanguages = new LanguageCollection();

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the name of the Language.
		/// </summary>
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		/// <summary>
		/// Gets or sets the string that acts as separators.
		/// </summary>
		public string Separators
		{
			get { return separators; }
			set { separators = value; }
		}

        /// <summary>
        /// Gets the collection of derived languages.
        /// </summary>
        public LanguageCollection DerivedLanguages
        {
            get { return derivedLanguages; }
        }

		/// <summary>
		/// Gets a string that can be given to a Open File/Save File Dialog to filter files.
		/// </summary>
		public string FileDialogFilters
		{
			get
			{
				FileType currentFileType = null;
				StringBuilder stringBuilder = new StringBuilder();

				for (int fileTypeIndex = 0; fileTypeIndex < this.FileTypes.Count; fileTypeIndex++)
				{
					currentFileType = this.FileTypes[fileTypeIndex] as FileType;

					stringBuilder.Append(currentFileType.Name + " (*" + currentFileType.Extension + ")");
					stringBuilder.Append("|");

					stringBuilder.Append("*");
					stringBuilder.Append(currentFileType.Extension);
					stringBuilder.Append("|");
				}

				return stringBuilder.ToString();
			}
		}

		/// <summary>
		/// Gets or sets the version of the Language.
		/// </summary>
		public long Version
		{
			get { return version; }
			set { version = value; }
		}

		/// <summary>
		/// Gets or sets the block of code that is the main Block of the Language.
		/// </summary>
		public Block MainBlock
		{
			get { return mainBlock; }
			set { mainBlock = value; }
		}

		/// <summary>
		/// Gets the ArrayList of the file types.
		/// </summary>
		public ArrayList FileTypes
		{
			get { return fileTypes; }
		}

		/// <summary>
		/// Gets the blocks in the Language.
		/// </summary>
		public Block[] Blocks
		{
			get
			{
				blocks.Clear();
				this.FillBlocks(mainBlock);

				Block[] newBlocks = new Block[blocks.Values.Count];

				int count = 0;
				foreach (Block current in blocks.Values)
				{
					newBlocks[count] = current;
					count++;
				}

				return newBlocks;
			}
		}

		/// <summary>
		/// Gets the styles of the Language.
		/// </summary>
		public TextStyle[] Styles
		{
			get
			{
				styles.Clear();
				Block[] blocks = this.Blocks;
				foreach (Block block in blocks)
				{
					styles[block.Style] = block.Style;

					foreach (Scope current in block.ScopePatterns)
					{

						if (current.Style != null)
							styles[current.Style] = current.Style;
					}

					foreach (PatternList current in block.KeywordsList)
					{
						if (current.Style != null)
							styles[current.Style] = current.Style;
					}

					foreach (PatternList current in block.OperatorsList)
					{
						if (current.Style != null)
							styles[current.Style] = current.Style;
					}
				}

				TextStyle[] newStyles = new TextStyle[styles.Values.Count];

				int i = 0;
				foreach (TextStyle st in styles.Values)
				{
					newStyles[i] = st;
					i++;
				}

				return newStyles;
			}
		}

		#endregion

		#region Methods

		#region Public

		/// <summary>
		/// Updates all the child blocks and styles in the Language.
		/// </summary>
		public void UpdateLists()
		{
			Block[] blocks = Blocks;
			foreach (Block block in blocks)
			{
				block.Parent = this;
				block.ResetLookupTable();

				block.KeywordsList.Parent = block;
				foreach (PatternList patterns in block.KeywordsList)
				{
					patterns.Parent = block.KeywordsList;

					foreach (Pattern pattern in patterns)
						block.AddToLookupTable(pattern);
				}

				block.OperatorsList.Parent = block;
				foreach (PatternList patterns in block.OperatorsList)
				{
					patterns.Parent = block.OperatorsList;

					foreach (Pattern pattern in patterns)
						block.AddToLookupTable(pattern);
				}

				block.BuildLookupTable();
			}
		}

		/// <summary>
		/// Raises the version of the Language.
		/// </summary>
		public void ChangeVersion()
		{
			this.Version++;
			if (this.Version > long.MaxValue - 10)
				this.Version = long.MinValue;
		}

		/// <summary>
		/// Returns a new Language from the given language definition file.
		/// </summary>
		/// <param name="filename">Path to the file.</param>
		/// <returns>A new Language from the given language definition file.</returns>
		public static Language FromSyntaxFile(string filename)
		{
			LanguageReader loader = new LanguageReader();
			return loader.Load(filename);
		}

		/// <summary>
		/// Returns a new Language from the given language definition file.
		/// </summary>
		/// <param name="filename">File Stream.</param>
		/// <returns>A new Language from the given language definition file.</returns>
		public static Language FromSyntaxFile(Stream stream)
		{
			LanguageReader loader = new LanguageReader();
			return loader.Load(stream);
		}

		/// <summary>
		/// Inserts the main block of the given Language into this Language's blocks.
		/// </summary>
		/// <param name="target">Target Language.</param>
		public void MergeByMainBlock(Language target)
		{
			Block[] blocks = this.Blocks;
			foreach (Block current in blocks)
				current.ChildBlocks.Insert(0, target.mainBlock);
		}

		/// <summary>
		/// Inserts the child blocks of the given Language into this Language's blocks.
		/// </summary>
		/// <param name="target">Target Language.</param>
		public void MergeByChildBlocks(Language target)
		{
			Block[] blocks = this.Blocks;
			foreach (Block current in blocks)
			{
				for (int i = target.mainBlock.ChildBlocks.Count - 1; i >= 0; i--)
				{
					Block child = target.mainBlock.ChildBlocks[i];
					current.ChildBlocks.Insert(0, child);
				}
			}
		}

		/// <summary>
		/// Saves the Language's styles in a configuration file.
		/// </summary>
		public void SaveStyles()
		{
			if (LanguageReader.UserCustomStyles == null)
				throw new IOException("Invalid user config dir.");
			else
			{
				if (Directory.Exists(LanguageReader.UserCustomStyles) == false)
					throw new IOException("Invalid user config path name, or path don't exist.");
			}

			string path = Path.Combine(LanguageReader.UserCustomStyles, name + ".conf");

			XmlTextWriter xmlTextWriter = new XmlTextWriter(path, Encoding.UTF8);

			xmlTextWriter.Formatting = Formatting.Indented;
			xmlTextWriter.WriteStartElement("styles");

			TextStyle[] styles = Styles;
			foreach (TextStyle style in styles)
			{
				xmlTextWriter.WriteStartElement("Style");

				xmlTextWriter.WriteAttributeString("Name", style.Name);

				xmlTextWriter.WriteAttributeString("ForeColor", style.ForeColor.Name);
				xmlTextWriter.WriteAttributeString("BackColor", style.BackColor.Name);

				xmlTextWriter.WriteAttributeString("Bold", style.Bold.ToString());
				xmlTextWriter.WriteAttributeString("Italic", style.Italic.ToString());
				xmlTextWriter.WriteAttributeString("Underline", style.Italic.ToString());

				xmlTextWriter.WriteEndElement();
			}

			xmlTextWriter.WriteEndElement();
			xmlTextWriter.Flush();
			xmlTextWriter.Close();
		}

		/// <summary>
		/// Returns the Language as a string.
		/// </summary>
		/// <returns>The Language as a string.</returns>
		public override string ToString()
		{
			return name;
		}

		#endregion

		#region Private

		/// <summary>
		/// Fills the list of blocks in the Language from the given block.
		/// </summary>
		/// <param name="block">Block to fill.</param>
		private void FillBlocks(Block block)
		{
			if (block == null)
				return;

			if (blocks[block] != null)
				return;

			blocks[block] = block;

			foreach (Block current in block.ChildBlocks)
				this.FillBlocks(current);

			foreach (Scope current in block.ScopePatterns)
			{
				this.FillBlocks(current.SpawnBlockOnEnd);
				this.FillBlocks(current.SpawnBlockOnStart);
			}
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of Language.
		/// </summary>
		public Language()
		{
		}
	}
}
